﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Kinect;
using System.Windows;
using System.Windows.Media;

namespace ZXing.Kinect
{
    public class KinectDecoder
    {
        public KinectPixelDataBarcodeReader QRCodeReader = new KinectPixelDataBarcodeReader();

        public enum DecodingModes
        {
            Full,
            TrackedJoints,
            PredefinedZone,
            Manual
        }

        public CoordinateMapper CoordinateMapper { get; set; }

        private DecodingModes _decodingMode = DecodingModes.Full;

        public DecodingModes DecodingMode
        {
            get { return _decodingMode; }
            set { _decodingMode = value; }
        }

        public JointType TrackedJoint { get; set; }

        public enum PredefinedZones
        {
            TopLeft,
            TopCenter,
            TopRight,
            MiddleLeft,
            MiddleCenter,
            MiddleRight,
            BottomLeft,
            BottomCenter,
            BottomRight,
        }

        public Rectangle Zone;

        private int _decodeEveryNFrames = 30;
        public int DecodeEveryNFrames { get { return _decodeEveryNFrames; } set { _decodeEveryNFrames = value; } }

        public KinectDecoder(int decodeEveryNFrames)
        {
            this.DecodeEveryNFrames = decodeEveryNFrames;
        }

        private byte[] _decodingZone = new byte[0];
        private uint zoneLength = 0;

        private int _frameCounter = 0;
        private bool _decodingInProgress = false;
        public ZXing.Result LastGoodResult { get; set; }
        private ZXing.Result _lastResult = null;

        public void LimitZone(FrameDescription frameDescription)
        {
            Zone.X = Math.Max(0, Math.Min(frameDescription.Width, Zone.X));
            Zone.Y = Math.Max(0, Math.Min(frameDescription.Height, Zone.Y));
            Zone.Width = Math.Max(0, Math.Min(frameDescription.Width - Zone.X, Zone.Width));
            Zone.Height = Math.Max(0, Math.Min(frameDescription.Height - Zone.Y, Zone.Height));
        }

        public void DetectZone(FrameDescription frameDescription, Body body)
        {
            if (DecodingMode == KinectDecoder.DecodingModes.TrackedJoints)
            {
                Joint? selectedJoint = GetSelectedJoint(body);
                if (selectedJoint.HasValue)
                    Zone = GetTrackedJointZone(frameDescription, selectedJoint.Value);
            }
            if (DecodingMode == KinectDecoder.DecodingModes.Full)
                Zone = new Rectangle(0, 0, frameDescription.Width, frameDescription.Height);

            LimitZone(frameDescription);
                            
        }


        private Rectangle GetTrackedJointZone(FrameDescription frameDescription, Joint selectedJoint)
        {
            ColorSpacePoint colorPoint = CoordinateMapper.MapCameraPointToColorSpace(selectedJoint.Position);

            CameraSpacePoint cameraSpace1 = selectedJoint.Position.Add(new CameraSpacePoint { X = -0.40f, Y = 0.40f });
            ColorSpacePoint colorPoint1 = CoordinateMapper.MapCameraPointToColorSpace(cameraSpace1);
            if (colorPoint1.X == float.NegativeInfinity) colorPoint1.X = 0;
            if (colorPoint1.X == float.PositiveInfinity) colorPoint1.X = frameDescription.Width;
            if (colorPoint1.Y == float.NegativeInfinity) colorPoint1.Y = 0;
            if (colorPoint1.Y == float.PositiveInfinity) colorPoint1.Y = frameDescription.Height;

            CameraSpacePoint cameraSpace2 = selectedJoint.Position.Add(new CameraSpacePoint { X = 0.40f, Y = -0.40f });
            ColorSpacePoint colorPoint2 = CoordinateMapper.MapCameraPointToColorSpace(cameraSpace2);
            if (colorPoint2.X == float.NegativeInfinity) colorPoint2.X = 0;
            if (colorPoint2.X == float.PositiveInfinity) colorPoint2.X = frameDescription.Width;
            if (colorPoint2.Y == float.NegativeInfinity) colorPoint2.Y = 0;
            if (colorPoint2.Y == float.PositiveInfinity) colorPoint2.Y = frameDescription.Height;

            return new Rectangle((int)colorPoint1.X, (int)colorPoint1.Y, (int)colorPoint2.X - (int)colorPoint1.X, (int)colorPoint2.Y - (int)colorPoint1.Y);

        }

        private Joint? GetSelectedJoint(Body selectedBody)
        {
            if (selectedBody == null || !selectedBody.IsTracked)
                return new Joint?();

            if (DecodingMode == KinectDecoder.DecodingModes.TrackedJoints)
            {
                return
                    selectedBody == null ?
                        new Joint?()
                        : selectedBody.Joints[TrackedJoint];
            }
            return new Joint?();
        }

        public void GeneratePredefinedZone(FrameDescription frameDescription, int col, int maxColumns, int row,int maxRows)
        {
            DecodingMode = DecodingModes.PredefinedZone;
            Zone.X = frameDescription.Width * col / maxColumns;
            Zone.Y = frameDescription.Height * row / maxRows;
            Zone.Width =frameDescription.Width / maxColumns;
            Zone.Height =frameDescription.Height / maxRows;

        }
        public void Decode(FrameDescription frameDescription, byte[] pixels, Body selectedBody)
        {
            DetectZone(frameDescription, selectedBody);
            if (Zone == Rectangle.Empty) return;

            MetaKinectPixelData metaPixelData = new MetaKinectPixelData();
            _frameCounter++;
            if (_frameCounter % (_decodeEveryNFrames / 3) == 0 && _lastResult == null)
                LastGoodResult = null;
            if (_frameCounter > _decodeEveryNFrames)
                _frameCounter = 0;
            if (_frameCounter % _decodeEveryNFrames == 0 && !_decodingInProgress)
            {
                int bytesPerPixel = (PixelFormats.Bgr32.BitsPerPixel + 7) / 8;
                uint newLength = (uint)(Zone.Width * Zone.Height * bytesPerPixel);
                if (_decodingZone.Length == 0 || zoneLength != newLength)
                {
                    _decodingZone = new byte[newLength];
                    zoneLength = newLength;
                }
                System.Buffer.BlockCopy(pixels, Zone.X * Zone.Y * bytesPerPixel, _decodingZone, 0, (int)zoneLength);

                metaPixelData = new MetaKinectPixelData
                {
                    PixelData = _decodingZone,
                    Width = Zone.Width,
                    Height = Zone.Height,
                };

                if (metaPixelData.PixelData != null && metaPixelData.PixelData.Length != 0)
                {
                    Thread th = new Thread(
                        new System.Threading.ThreadStart(
                            () =>
                            {
                                _decodingInProgress = true;
                                _lastResult = QRCodeReader.Decode(metaPixelData);
                                if (_lastResult != null && _lastResult.ResultPoints.Length >= 3)
                                {
                                    LastGoodResult = _lastResult;
                                    for (int i = 0; i < _lastResult.ResultPoints.Length; i++)
                                    {
                                        _lastResult.ResultPoints[i] = new ResultPoint(Zone.Width - _lastResult.ResultPoints[i].X, _lastResult.ResultPoints[i].Y);
                                    }
                                    _frameCounter = 0;
                                }
                                _decodingInProgress = false;
                            }));
                    th.Start();
                }
            }
        }
    }
}
